package com.basker.pisces.domain.command.run;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.support.TransactionTemplate;

import com.basker.pisces.core.datasource.IDataSourceManager;
import com.basker.pisces.domain.command.CommandResult;
import com.basker.pisces.domain.command.meta.TransactionalCommand;

/**
 * 支持事务的命令运行器,{@link #invoke(Object[])}方法会开启事务。{@link #transactionalInvoke(Object[])}在事务内执行
 *
 * @param <TCommand>
 * @author hangwen
 */
public abstract class TransactionalCommandRunner<TCommand extends TransactionalCommand>
        extends CommandRunner<TCommand> {

    @Autowired
    private IDataSourceManager dataSourceManager;

    @Autowired
    @Qualifier("dynamicDataSourceTransactionManager")
    private PlatformTransactionManager transactionManager;

    protected Isolation getIsolation() {
        return Isolation.REPEATABLE_READ;
    }

    /**
     * 命令执行完成，但是事务尚未提交
     *
     * @param dataObjects
     */
    protected void onBeforeTransactionalCommit(Object[] dataObjects) {

    }

    /**
     * 通过验证后，事务开始前调用
     *
     * @param dataObjects
     */
    protected void onBeforeTransactionalInvoke(Object[] dataObjects) {

    }

    protected abstract CommandResult transactionalInvoke(Object[] dataObjects);

    final void beforeTransactionalCommit(Object[] dataObjects) {
        this.onBeforeTransactionalCommit(dataObjects);

        this.eventPublisher.applyProcessBeforeTransactionalCommit(dataObjects);
    }

    final void beforeTransactionalInvoke(Object[] dataObjects) {
        this.onBeforeTransactionalInvoke(dataObjects);

        this.eventPublisher.applyProcessBeforeTransactionalInvoke(dataObjects);
    }

    @Override
    final CommandResult invoke(Object[] dataObjects) {
        this.beforeTransactionalInvoke(dataObjects);

        String schema = this.getContext().getEntity().getDataObjectMeta().getSchema();
        dataSourceManager.setContextDataSourceName(schema);

        TransactionTemplate transactionTemplate = this.createTransactionTemplate(this.getIsolation());

        return transactionTemplate.execute(status -> {
            CommandResult result = this.transactionalInvoke(dataObjects);

            this.beforeTransactionalCommit(dataObjects);

            return result;
        });
    }

    private TransactionTemplate createTransactionTemplate(Isolation transactionIsolation) {
        TransactionTemplate template = new TransactionTemplate(transactionManager);
        template.setName(this.getClass().getSimpleName() + "'s transaction definition");
        template.setIsolationLevel(transactionIsolation.value());

        return template;
    }

}
